"""
层次分析法（AHP）评价模型

问题描述：
    层次分析法（Analytic Hierarchy Process, AHP）是一种定性和定量相结合的
    多准则决策分析方法。它将复杂的决策问题分解为多个层次和因素，
    通过专家判断或数据分析构造判断矩阵，计算权重，为决策提供定量依据。

算法原理：
    AHP的基本步骤：
    1. 构建层次结构模型（目标层-准则层-方案层）
    2. 构造两两比较的判断矩阵
    3. 计算权重向量（特征值法）
    4. 一致性检验
    5. 层次总排序

核心概念：
    - 判断矩阵：专家对各因素重要性进行两两比较的矩阵
    - 权重向量：表示各因素相对重要性的向量
    - 一致性比率(CR)：检验判断矩阵逻辑一致性的指标
    - 层次总排序：自上而下逐层计算综合权重

应用场景：
    AHP广泛应用于：
    - 供应商选择
    - 项目评估
    - 风险评价
    - 城市规划
    - 综合评价问题

作者：斯黄
日期：2025年3月4日
"""

import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
from scipy.linalg import eig

class AHP:
    def __init__(self):
        # 一致性指标RI表（1-10阶）
        self.RI = {1: 0, 2: 0, 3: 0.58, 4: 0.90, 5: 1.12, 6: 1.24, 
                   7: 1.32, 8: 1.41, 9: 1.45, 10: 1.49}
    
    def normalize_matrix(self, matrix):
        """归一化判断矩阵"""
        matrix = np.array(matrix, dtype=float)
        return matrix / matrix.sum(axis=0)
    
    def geometric_mean_method(self, matrix):
        """几何平均法计算权重"""
        matrix = np.array(matrix, dtype=float)
        n = matrix.shape[0]
        # 计算每行几何平均值
        weights = np.power(np.prod(matrix, axis=1), 1/n)
        # 归一化
        weights = weights / weights.sum()
        return weights
    
    def eigenvalue_method(self, matrix):
        """特征值法计算权重"""
        matrix = np.array(matrix, dtype=float)
        eigenvalues, eigenvectors = eig(matrix)
        # 找到最大特征值对应的特征向量
        max_idx = np.argmax(eigenvalues.real)
        max_eigenvector = eigenvectors[:, max_idx].real
        # 归一化并确保为正数
        weights = np.abs(max_eigenvector) / np.sum(np.abs(max_eigenvector))
        lambda_max = eigenvalues[max_idx].real
        return weights, lambda_max
    
    def consistency_check(self, matrix):
        """一致性检验"""
        matrix = np.array(matrix, dtype=float)
        n = matrix.shape[0]
        
        if n <= 2:
            return True, 0, 0  # 2阶及以下矩阵自动满足一致性
        
        weights, lambda_max = self.eigenvalue_method(matrix)
        
        # 计算一致性指标CI
        CI = (lambda_max - n) / (n - 1)
        
        # 计算一致性比率CR
        CR = CI / self.RI[n]
        
        # CR < 0.1认为具有满意的一致性
        is_consistent = CR < 0.1
        
        return is_consistent, CI, CR
    
    def ahp_analysis(self, matrix, labels=None):
        """完整的AHP分析"""
        matrix = np.array(matrix, dtype=float)
        
        print("=== 判断矩阵 ===")
        if labels:
            df = pd.DataFrame(matrix, index=labels, columns=labels)
            print(df)
        else:
            print(matrix)
        
        # 一致性检验
        is_consistent, CI, CR = self.consistency_check(matrix)
        print(f"\n=== 一致性检验 ===")
        print(f"最大特征值 λmax: {self.eigenvalue_method(matrix)[1]:.4f}")
        print(f"一致性指标 CI: {CI:.4f}")
        print(f"一致性比率 CR: {CR:.4f}")
        print(f"一致性检验: {'通过' if is_consistent else '不通过'}")
        
        if not is_consistent:
            print("警告：判断矩阵不满足一致性要求，需要调整判断值！")
        
        # 计算权重
        weights_geo = self.geometric_mean_method(matrix)
        weights_eig = self.eigenvalue_method(matrix)[0]
        
        print(f"\n=== 权重计算结果 ===")
        weight_df = pd.DataFrame({
            '几何平均法': weights_geo,
            '特征值法': weights_eig
        }, index=labels if labels else range(len(weights_geo)))
        print(weight_df)
        
        return weights_eig, is_consistent, CR

# 示例：大学生综合评价问题
def university_student_evaluation():
    print("=== 大学生综合评价AHP分析 ===")
    print("评价指标：学习成绩、科研能力、社会实践、综合素质")
    
    # 构造准则层判断矩阵
    criteria_matrix = [
        [1,   2,   3,   4],    # 学习成绩
        [1/2, 1,   2,   3],    # 科研能力  
        [1/3, 1/2, 1,   2],    # 社会实践
        [1/4, 1/3, 1/2, 1]     # 综合素质
    ]
    
    criteria_labels = ['学习成绩', '科研能力', '社会实践', '综合素质']
    
    ahp = AHP()
    criteria_weights, is_consistent, CR = ahp.ahp_analysis(criteria_matrix, criteria_labels)
    
    # 方案层（假设有3个学生）
    student_labels = ['学生A', '学生B', '学生C']
    
    # 学习成绩子判断矩阵
    academic_matrix = [
        [1,   1/2, 2],
        [2,   1,   3], 
        [1/2, 1/3, 1]
    ]
    
    # 科研能力子判断矩阵
    research_matrix = [
        [1, 3, 1/2],
        [1/3, 1, 1/4],
        [2, 4, 1]
    ]
    
    # 社会实践子判断矩阵
    practice_matrix = [
        [1, 1/3, 2],
        [3, 1, 4],
        [1/2, 1/4, 1]
    ]
    
    # 综合素质子判断矩阵
    quality_matrix = [
        [1, 2, 1/3],
        [1/2, 1, 1/4],
        [3, 4, 1]
    ]
    
    sub_matrices = [academic_matrix, research_matrix, practice_matrix, quality_matrix]
    sub_labels = ['学习成绩', '科研能力', '社会实践', '综合素质']
    
    # 计算各子矩阵权重
    student_weights = []
    print(f"\n=== 方案层权重计算 ===")
    
    for i, (matrix, label) in enumerate(zip(sub_matrices, sub_labels)):
        print(f"\n--- {label} ---")
        weights, _, _ = ahp.ahp_analysis(matrix, student_labels)
        student_weights.append(weights)
    
    # 计算综合权重
    student_weights = np.array(student_weights).T  # 转置使每行为一个学生
    final_scores = student_weights @ criteria_weights
    
    print(f"\n=== 最终评价结果 ===")
    results_df = pd.DataFrame({
        '学习成绩': student_weights[:, 0],
        '科研能力': student_weights[:, 1], 
        '社会实践': student_weights[:, 2],
        '综合素质': student_weights[:, 3],
        '综合得分': final_scores
    }, index=student_labels)
    
    print(results_df)
    
    # 排序
    ranking = results_df.sort_values('综合得分', ascending=False)
    print(f"\n排名结果:")
    for i, (student, score) in enumerate(zip(ranking.index, ranking['综合得分'])):
        print(f"{i+1}. {student}: {score:.4f}")
    
    return results_df, criteria_weights

# 可视化函数
def visualize_ahp_results(results_df, criteria_weights):
    fig, axes = plt.subplots(2, 2, figsize=(15, 10))
    
    # 1. 准则权重饼图
    criteria_labels = ['学习成绩', '科研能力', '社会实践', '综合素质']
    axes[0, 0].pie(criteria_weights, labels=criteria_labels, autopct='%1.1f%%', startangle=90)
    axes[0, 0].set_title('准则权重分布')
    
    # 2. 学生综合得分柱状图
    students = results_df.index
    scores = results_df['综合得分']
    bars = axes[0, 1].bar(students, scores, color=['skyblue', 'lightgreen', 'salmon'])
    axes[0, 1].set_title('学生综合得分对比')
    axes[0, 1].set_ylabel('综合得分')
    
    # 添加数值标签
    for bar, score in zip(bars, scores):
        axes[0, 1].text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.01, 
                        f'{score:.3f}', ha='center', va='bottom')
    
    # 3. 各指标得分雷达图
    angles = np.linspace(0, 2*np.pi, len(criteria_labels), endpoint=False).tolist()
    angles += angles[:1]  # 闭合图形
    
    ax = axes[1, 0]
    ax = plt.subplot(2, 2, 3, projection='polar')
    
    colors = ['blue', 'green', 'red']
    for i, student in enumerate(students):
        values = results_df.loc[student, criteria_labels].tolist()
        values += values[:1]  # 闭合图形
        ax.plot(angles, values, 'o-', linewidth=2, label=student, color=colors[i])
        ax.fill(angles, values, alpha=0.25, color=colors[i])
    
    ax.set_xticks(angles[:-1])
    ax.set_xticklabels(criteria_labels)
    ax.set_title('各指标得分雷达图')
    ax.legend()
    
    # 4. 指标权重与学生得分对比
    x = np.arange(len(criteria_labels))
    width = 0.2
    
    axes[1, 1].bar(x - width, criteria_weights, width, label='准则权重', alpha=0.8)
    for i, student in enumerate(students):
        student_scores = results_df.loc[student, criteria_labels]
        axes[1, 1].bar(x + (i-1)*width, student_scores, width, 
                      label=f'{student}得分', alpha=0.8)
    
    axes[1, 1].set_xlabel('评价指标')
    axes[1, 1].set_ylabel('权重/得分')
    axes[1, 1].set_title('准则权重与学生各指标得分对比')
    axes[1, 1].set_xticks(x)
    axes[1, 1].set_xticklabels(criteria_labels, rotation=45)
    axes[1, 1].legend()
    
    plt.tight_layout()
    plt.show()

# 敏感性分析
def sensitivity_analysis(base_matrix, labels, vary_index=(0,1)):
    """敏感性分析：分析判断矩阵中某个元素变化对权重的影响"""
    print(f"\n=== 敏感性分析 ===")
    print(f"分析第{vary_index[0]+1}行第{vary_index[1]+1}列元素变化的影响")
    
    ahp = AHP()
    base_weights = ahp.eigenvalue_method(base_matrix)[0]
    
    # 变化范围
    original_value = base_matrix[vary_index[0]][vary_index[1]]
    variation_range = np.linspace(0.5, 3.0, 26)  # 从0.5到3.0变化
    
    weights_history = []
    for factor in variation_range:
        # 修改判断矩阵
        modified_matrix = np.array(base_matrix, dtype=float)
        modified_matrix[vary_index[0], vary_index[1]] = original_value * factor
        modified_matrix[vary_index[1], vary_index[0]] = 1 / (original_value * factor)
        
        # 计算新权重
        try:
            new_weights = ahp.eigenvalue_method(modified_matrix)[0]
            weights_history.append(new_weights)
        except:
            weights_history.append(base_weights)
    
    weights_history = np.array(weights_history)
    
    # 绘制敏感性分析结果
    plt.figure(figsize=(12, 6))
    
    for i, label in enumerate(labels):
        plt.plot(variation_range, weights_history[:, i], 'o-', label=label, linewidth=2)
    
    plt.axvline(x=1.0, color='red', linestyle='--', alpha=0.7, label='原始值')
    plt.xlabel('相对变化倍数')
    plt.ylabel('权重值')
    plt.title(f'敏感性分析：元素({vary_index[0]+1},{vary_index[1]+1})变化对权重的影响')
    plt.legend()
    plt.grid(True, alpha=0.3)
    plt.show()
    
    # 计算敏感性系数
    print("各指标敏感性系数（权重变化率/元素变化率）:")
    for i, label in enumerate(labels):
        sensitivity = np.gradient(weights_history[:, i]) / np.gradient(variation_range)
        avg_sensitivity = np.mean(np.abs(sensitivity))
        print(f"{label}: {avg_sensitivity:.4f}")

if __name__ == "__main__":
    # 执行大学生评价示例
    results_df, criteria_weights = university_student_evaluation()
    
    # 可视化结果
    visualize_ahp_results(results_df, criteria_weights)
    
    # 敏感性分析
    criteria_matrix = [
        [1,   2,   3,   4],
        [1/2, 1,   2,   3],
        [1/3, 1/2, 1,   2],
        [1/4, 1/3, 1/2, 1]
    ]
    criteria_labels = ['学习成绩', '科研能力', '社会实践', '综合素质']
    
    sensitivity_analysis(criteria_matrix, criteria_labels, vary_index=(0,1)) 